﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.DirectoryServices;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.Deployment.WindowsInstaller;

namespace UpdateIIS
{
    public class CustomActions
    {

        #region Constants 

        const int MD_AUTH_ANONYMOUS = 0x00000001;
        const String VirtualDirSchema = "IIsWebVirtualDir";
        const string WebServerSchema = "IIsWebServer";

        const string AgentCommunicationsDir = "AgentCommunications";
        const string CrmWebSiteProperty = "CRM_WEBSITE";
        const string CrmWebSitePathProperty = "CRM_WEBSITEPATH";

        #endregion

        private static Session _session;

        #region Custom action entry points

        [CustomAction]
        public static ActionResult AddVirtualDirectory(Session session)
        {
            _session = session;
            Log("Begin AddVirtualDirectory");

            string vdirPath = _session.CustomActionData[CrmWebSitePathProperty] + AgentCommunicationsDir;

            // There should be no unhandled exceptions.  In the event there are we want them in the msi log.
            try
            {
                return DoAddVirtualDirectory(_session.CustomActionData[CrmWebSiteProperty], AgentCommunicationsDir, vdirPath);
            }
            catch (Exception ex)
            {
                Log(ex.ToString());
                return ActionResult.Failure;
            }
        }

        [CustomAction]
        public static ActionResult DeleteVirtualDirectory(Session session)
        {
            _session = session;
            Log("Begin DeleteVirtualDirectory");


            // There should be no unhandled exceptions.  In the event there are we want them in the msi log.
            try
            {
                return DoDeleteVirtualDirectory(_session.CustomActionData[CrmWebSiteProperty], AgentCommunicationsDir);
            }
            catch (Exception ex)
            {
                Log(ex.ToString());
                return ActionResult.Failure;
            }
        }
        #endregion 

        #region Helper methods accessible by test driver 

        /// <summary>
        /// Delete the virtual directory
        /// </summary>
        /// <param name="WebSite"></param>
        /// <param name="VirtualDirName"></param>
        /// <returns></returns>
        public static ActionResult DoDeleteVirtualDirectory(string WebSite, string VirtualDirName)
        {
            string webSiteRoot = ExtractWebsiteRoot(WebSite);
            DirectoryEntry w3svcRoot = new DirectoryEntry(webSiteRoot);

            Log("Deleteing virtual directory : {0}", VirtualDirName);

            try
            {
                DirectoryEntry agentCommuncations = w3svcRoot.Children.Find(VirtualDirName, VirtualDirSchema);
                w3svcRoot.Children.Remove(agentCommuncations);
            }
            catch (DirectoryNotFoundException ex)
            {
                Log("Error - Unable to find virtual directory : {0}\n{1}", VirtualDirName, ex.ToString());
                return ActionResult.Failure;
            }
            catch (COMException ex)
            {
                Log("Error - Directory entry does not exist : {0}\n{1}", webSiteRoot, ex.ToString());
                return ActionResult.Failure;
            }

            return ActionResult.Success;
        }

        /// <summary>
        /// Add the new virtual directory and add anonymous access.  If the directory already exists
        /// this method will fail.
        /// </summary>
        /// <param name="WebSite"></param>
        /// <param name="VirtualDirName"></param>
        /// <param name="Path"></param>
        /// <returns></returns>
        public static ActionResult DoAddVirtualDirectory(string WebSite, string VirtualDirName, string Path)
        {
            string webSiteRoot = ExtractWebsiteRoot(WebSite);
            DirectoryEntry w3svcRoot = new DirectoryEntry(webSiteRoot);
            DirectoryEntry agentCommunications;

            Log("Configuring anonymous access for '{0}' under '{1}'", VirtualDirName, webSiteRoot);

            try
            {
                agentCommunications = w3svcRoot.Children.Add(VirtualDirName, VirtualDirSchema);

                Log("Added virtual directory '{0}' under '{1}'", VirtualDirName, webSiteRoot);
            }
            catch (COMException ex)
            {
                // The virtual directory already exists.
                Log("Error : Failed to create virtual directory '{0}'.  Delete this virtual directory before installing. \n{1}",
                    VirtualDirName,
                    ex.ToString());

                return ActionResult.Failure;
            }

            // Set the path and submit the changes so we pick up the defaults.  This must be done before updating the AuthFlags.
            agentCommunications.Properties["Path"][0] = Path;
            agentCommunications.CommitChanges();

            // Update AuthFlags to accept anonymous access.
            int originalAuth = (int)agentCommunications.Properties["AuthFlags"].Value;
            agentCommunications.Properties["AuthFlags"][0] = originalAuth | MD_AUTH_ANONYMOUS;
            agentCommunications.CommitChanges();
            
            return ActionResult.Success;
        }

        #endregion

        private static string ExtractWebsiteRoot(string sessionProperty)
        {
            string basePath = Regex.Replace(sessionProperty, "/LM/", "/LocalHost/");
            return String.Format("IIS:/{0}/Root", basePath);
        }

        /// <summary>
        /// Logging method which writes to the session log if available.
        /// </summary>
        /// <param name="msg"></param>
        private static void Log(string msg)
        {
            if (null != _session)
            {
                _session.Log(msg);
            }

            Debug.WriteLine(msg);
        }

        /// <summary>
        /// Logging method which writes to the session log if available.
        /// </summary>
        /// <param name="format"></param>
        /// <param name="args"></param>
        private static void Log(string format, params object[] args)
        {
            if (null != _session)
            {
                _session.Log(format, args);
            }

            Debug.WriteLine(String.Format(format, args));
        }
    }
}
